# Part of Odoo. See LICENSE file for full copyright and licensing details.

import base64
import io

from PIL import Image

from odoo.fields import Command
from odoo.tests import HttpCase, tagged

from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser


def _create_image(color='black', dims=(1920, 1080), format='JPEG'):
    f = io.BytesIO()
    Image.new('RGB', dims, color).save(f, format)  # type: ignore
    f.seek(0)
    return base64.b64encode(f.read())


@tagged('post_install', '-at_install')
class TestWebsiteSaleImage(HttpCaseWithWebsiteUser):

    # registry_test_mode = False  # uncomment to save the product to test in browser

    def test_01_admin_shop_zoom_tour(self):
        color_red = '#CD5C5C'
        name_red = 'Indian Red'

        color_green = '#228B22'
        name_green = 'Forest Green'

        color_blue = '#4169E1'
        name_blue = 'Royal Blue'

        self.env['product.pricelist'].sudo().search([]).action_archive()

        # create the color attribute
        product_attribute = self.env['product.attribute'].create({
            'name': 'Beautiful Color',
            'display_type': 'color',
            'value_ids': [
                Command.create({
                    'name': name_red,
                    'html_color': color_red,
                    'sequence': 1,
                }),
                Command.create({
                    'name': name_green,
                    'html_color': color_green,
                    'sequence': 2,
                }),
                Command.create({
                    'name': name_blue,
                    'html_color': color_blue,
                    'sequence': 3,
                }),
            ]
        })

        # first image (blue) for the template
        blue_image = _create_image(color=color_blue)

        # second image (red) for the variant 1, small image (no zoom)
        red_image = _create_image(color=color_red, dims=(800, 500))

        # second image (green) for the variant 2, big image (zoom)
        green_image = _create_image(color=color_green)

        # Template Extra Image 1
        image_gif = _create_image(dims=(124, 147), format='GIF')

        # Template Extra Image 2
        image_svg = base64.b64encode(b'<svg></svg>')

        # Red Variant Extra Image 1
        image_bmp = _create_image(dims=(767, 247), format='BMP')

        # Green Variant Extra Image 1
        image_png = _create_image(dims=(2147, 3251), format='PNG')

        # create the template, without creating the variants
        template = self.env['product.template'].create({
            'name': 'A Colorful Image',
            'product_template_image_ids': [
                Command.create({'name': 'image 1', 'image_1920': image_gif}),
                Command.create({'name': 'image 4', 'image_1920': image_svg}),
            ],
            'attribute_line_ids': [
                Command.create({
                    'attribute_id': product_attribute.id,
                    'value_ids': [Command.set(product_attribute.value_ids.ids)],
                })
            ]
        })

        line = template.attribute_line_ids
        value_red = line.product_template_value_ids[0]
        value_green = line.product_template_value_ids[1]

        # set a different price on the variants to differentiate them
        product_template_attribute_values = self.env['product.template.attribute.value'].search([('product_tmpl_id', '=', template.id)])

        for val in product_template_attribute_values:
            if val.name == name_red:
                val.price_extra = 10
            else:
                val.price_extra = 20

        # Get RED variant, and set image to blue (will be set on the template
        # because the template image is empty and there is only one variant)
        product_red = template._get_variant_for_combination(value_red)
        product_red.write({
            'image_1920': blue_image,
            'product_variant_image_ids': [(0, 0, {'name': 'image 2', 'image_1920': image_bmp})],
        })

        self.assertEqual(template.image_1920, blue_image)

        # Get the green variant
        product_green = template._get_variant_for_combination(value_green)
        product_green.write({
            'image_1920': green_image,
            'product_variant_image_ids': [(0, 0, {'name': 'image 3', 'image_1920': image_png})],
        })

        # now set the red image on the first variant, that works because
        # template image is not empty anymore and we have a second variant
        product_red.image_1920 = red_image

        # Verify image_1920 size > 1024 can be zoomed
        self.assertTrue(template.can_image_1024_be_zoomed)
        self.assertFalse(template.product_template_image_ids[0].can_image_1024_be_zoomed)
        self.assertFalse(template.product_template_image_ids[1].can_image_1024_be_zoomed)
        self.assertFalse(product_red.can_image_1024_be_zoomed)
        self.assertFalse(product_red.product_variant_image_ids[0].can_image_1024_be_zoomed)
        self.assertTrue(product_green.can_image_1024_be_zoomed)
        self.assertTrue(product_green.product_variant_image_ids[0].can_image_1024_be_zoomed)

        # jpeg encoding is changing the color a bit
        jpeg_blue = (65, 105, 227)
        jpeg_red = (205, 93, 92)
        jpeg_green = (34, 139, 34)

        # Verify original size: keep original
        image = Image.open(io.BytesIO(base64.b64decode(template.image_1920)))
        self.assertEqual(image.size, (1920, 1080))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
        image = Image.open(io.BytesIO(base64.b64decode(product_red.image_1920)))
        self.assertEqual(image.size, (800, 500))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
        image = Image.open(io.BytesIO(base64.b64decode(product_green.image_1920)))
        self.assertEqual(image.size, (1920, 1080))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")

        # Verify 1024 size: keep aspect ratio
        image = Image.open(io.BytesIO(base64.b64decode(template.image_1024)))
        self.assertEqual(image.size, (1024, 576))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
        image = Image.open(io.BytesIO(base64.b64decode(product_red.image_1024)))
        self.assertEqual(image.size, (800, 500))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
        image = Image.open(io.BytesIO(base64.b64decode(product_green.image_1024)))
        self.assertEqual(image.size, (1024, 576))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")

        # Verify 512 size: keep aspect ratio
        image = Image.open(io.BytesIO(base64.b64decode(template.image_512)))
        self.assertEqual(image.size, (512, 288))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
        image = Image.open(io.BytesIO(base64.b64decode(product_red.image_512)))
        self.assertEqual(image.size, (512, 320))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
        image = Image.open(io.BytesIO(base64.b64decode(product_green.image_512)))
        self.assertEqual(image.size, (512, 288))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")

        # Verify 256 size: keep aspect ratio
        image = Image.open(io.BytesIO(base64.b64decode(template.image_256)))
        self.assertEqual(image.size, (256, 144))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
        image = Image.open(io.BytesIO(base64.b64decode(product_red.image_256)))
        self.assertEqual(image.size, (256, 160))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
        image = Image.open(io.BytesIO(base64.b64decode(product_green.image_256)))
        self.assertEqual(image.size, (256, 144))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")

        # Verify 128 size: keep aspect ratio
        image = Image.open(io.BytesIO(base64.b64decode(template.image_128)))
        self.assertEqual(image.size, (128, 72))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
        image = Image.open(io.BytesIO(base64.b64decode(product_red.image_128)))
        self.assertEqual(image.size, (128, 80))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
        image = Image.open(io.BytesIO(base64.b64decode(product_green.image_128)))
        self.assertEqual(image.size, (128, 72))
        self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")

        # self.env.cr.commit()  # uncomment to save the product to test in browser

        # Make sure we have zoom on click
        self.env['ir.ui.view'].with_context(active_test=False).search(
            [('key', '=', 'website_sale.product_picture_magnify_click')]
        ).write({'active': True})

        # Ensure that no pricelist is available during the test.
        # This ensures that tours with triggers on the amounts will run properly.
        self.env['product.pricelist'].search([]).action_archive()

        self.start_tour("/", 'shop_zoom', login="website_user")

        # CASE: unlink move image to fallback if fallback image empty
        template.image_1920 = False
        product_red.unlink()
        self.assertEqual(template.image_1920, red_image)

        # CASE: unlink does nothing special if fallback image already set
        self.env['product.product'].create({
            'product_tmpl_id': template.id,
            'image_1920': green_image,
        }).unlink()
        self.assertEqual(template.image_1920, red_image)

        # CASE: display variant image first if set
        self.assertEqual(product_green._get_images()[0].image_1920, green_image)

        # CASE: display variant fallback after variant o2m, correct fallback
        # write on the variant field, otherwise it will write on the fallback
        product_green.image_variant_1920 = False
        images = product_green._get_images()
        # images on fields are resized to max 1920
        image_png = Image.open(io.BytesIO(base64.b64decode(images[1].image_1920)))
        self.assertEqual(images[0].image_1920, red_image)
        self.assertEqual(image_png.size, (1268, 1920))
        self.assertEqual(images[2].image_1920, image_gif)
        self.assertEqual(images[3].image_1920, image_svg)

        # CASE: When uploading a product variant image
        # we don't want the default_product_tmpl_id from the context to be applied if we have a product_variant_id set
        # we want the default_product_tmpl_id from the context to be applied if we don't have a product_variant_id set

        additionnal_context = {'default_product_tmpl_id': template.id}

        product = self.env['product.product'].create({
            'product_tmpl_id': template.id,
        })

        product_image = self.env['product.image'].with_context(**additionnal_context).create([{
            'name': 'Template image',
            'image_1920': red_image,
        }, {
            'name': 'Variant image',
            'image_1920': blue_image,
            'product_variant_id': product.id,
        }])

        template_image = product_image.filtered(lambda i: i.name == 'Template image')
        variant_image = product_image.filtered(lambda i: i.name == 'Variant image')

        self.assertEqual(template_image.product_tmpl_id.id, template.id)
        self.assertFalse(template_image.product_variant_id.id)
        self.assertFalse(variant_image.product_tmpl_id.id)
        self.assertEqual(variant_image.product_variant_id.id, product.id)

    def test_02_image_holder(self):
        image = _create_image(color='#FF0000', dims=(800, 500))

        # create the color attribute
        product_attribute = self.env['product.attribute'].create({
            'name': 'Beautiful Color',
            'display_type': 'color',
            'value_ids': [
                Command.create({
                    'name': 'Red',
                    'sequence': 1,
                }),
                Command.create({
                    'name': 'Green',
                    'sequence': 2,
                }),
                Command.create({
                    'name': 'Blue',
                    'sequence': 3,
                }),
            ]
        })

        # create the template, without creating the variants
        template = self.env['product.template'].with_context(create_product_product=False).create({
            'name': 'Test subject',
        })

        # when there are no variants, the image must be obtained from the template
        self.assertEqual(template, template._get_image_holder())

        # set the color attribute and values on the template
        line = self.env['product.template.attribute.line'].create([{
            'attribute_id': product_attribute.id,
            'product_tmpl_id': template.id,
            'value_ids': [Command.set(product_attribute.value_ids.ids)]
        }])
        value_red = line.product_template_value_ids[0]
        product_red = template._get_variant_for_combination(value_red)
        product_red.image_variant_1920 = image

        value_green = line.product_template_value_ids[1]
        product_green = template._get_variant_for_combination(value_green)
        product_green.image_variant_1920 = image

        # when there are no template image but there are variants, the image must be obtained from the first variant
        self.assertEqual(product_red, template._get_image_holder())

        product_red.action_archive()

        # but when some variants are not available, the image must be obtained from the first available variant
        self.assertEqual(product_green, template._get_image_holder())

        template.image_1920 = image

        # when there is a template image, the image must be obtained from the template
        self.assertEqual(template, template._get_image_holder())

@tagged('post_install', '-at_install')
class TestWebsiteSaleRemoveImage(HttpCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        # First image (blue) for the template.
        color_blue = '#4169E1'
        name_blue = 'Royal Blue'
        # Red for the variant.
        color_red = '#CD5C5C'
        name_red = 'Indian Red'
        # Green for the replacement
        color_green = '#228B22'

        # Attachment needed for the replacement of images
        cls.env['ir.attachment'].create({
            'public': True,
            'name': 'green.jpg',
            'type': 'binary',
            'datas': _create_image(color=color_green)
        })

        # Create the color attribute.
        cls.product_attribute = cls.env['product.attribute'].create({
            'name': 'Beautiful Color',
            'display_type': 'color',
        })

        # create the color attribute values
        cls.attr_values = cls.env['product.attribute.value'].create([{
            'name': name_blue,
            'attribute_id': cls.product_attribute.id,
            'html_color': color_blue,
            'sequence': 1,
        }, {
            'name': name_red,
            'attribute_id': cls.product_attribute.id,
            'html_color': color_red,
            'sequence': 2,
        },
        ])

        cls.template = cls.env['product.template'].with_context(create_product_product=False).create({
            'name': 'Test Remove Image',
            'image_1920': _create_image(color=color_blue),
        })

    def test_website_sale_add_and_remove_main_product_image_no_variant(self):
        self.product = self.env['product.product'].create({
            'product_tmpl_id': self.template.id,
        })

        self.start_tour(self.env['website'].get_client_action_url('/'), 'add_and_remove_main_product_image_no_variant', login='admin')
        self.assertFalse(self.template.image_1920)
        self.assertFalse(self.product.image_1920)

    def test_website_sale_remove_main_product_image_with_variant(self):
        # Set the color attribute and values on the template.
        self.env['product.template.attribute.line'].create([{
            'attribute_id': self.product_attribute.id,
            'product_tmpl_id': self.template.id,
            'value_ids': [(6, 0, self.attr_values.ids)]
        }])
        self.product = self.env['product.product'].create({
            'product_tmpl_id': self.template.id,
        })
        self.start_tour(self.env['website'].get_client_action_url('/'), 'remove_main_product_image_with_variant', login='admin')
        self.assertFalse(self.template.image_1920)
        self.assertFalse(self.product.image_1920)
